#ifndef __MATRIZ_H__
#define __MATRIZ_H__

#include <iostream>
#include <vector>

using namespace std;

/**
 * matriz 
 */

template<class TIPO>
class matriz
{
	public:
	
		matriz();
		
		/** 
		* Construye una matriz de dimensión nxm.
		* Los valores pueden haber sido no inicializados.
		*/    
		matriz(unsigned int n, unsigned int m);

		/** 
		* Construye una matriz de dimensión igual a size,
		* inicializando todos sus valores con initialValue
		*/ 
		matriz(unsigned int n, unsigned int m, TIPO valor);  

		unsigned int cant_filas();
		unsigned int cant_columnas();
	
		vector<TIPO>& get_fila(unsigned int fila);
		void set_fila(unsigned int fila, vector<TIPO>& vec);
		
		TIPO get_elem(unsigned int fila, unsigned int columna);		
		void set_elem(unsigned int fila, unsigned int columna, TIPO elem);
		
		vector<TIPO>& operator[](unsigned int i);
		
		matriz<TIPO>& trasponer(matriz<TIPO>& m );

		/**
		 * Agrega una fila a la matriz como ultima fila, extendiendo la dimension a n+1 x m. Si el vector es mas largo que la cantidad de columnas
		 * rellena con el valor por defecto del tipo T las demas filas. Si es mas corto, rellena la 
		 * fila que se esta agregando.
		 */		 
		void push_fila(std::vector<TIPO>& fila); 		 
		
		void mostrar(std::ostream& out) const; 

	private:

		unsigned int _filas;
		unsigned int _columnas;
		vector< vector<TIPO> > _elementos;

};

template <class TIPO>
ostream& operator<<(ostream& out, const matriz<TIPO>& m);

/* Implementaciones... Si, tienen que estar aca para que anden los templates */

template <class TIPO>
matriz<TIPO>::matriz() : _filas(0), _columnas(0)
{}

template <class TIPO>
matriz<TIPO>::matriz(unsigned int n, unsigned int m) : _filas(n), _columnas(m), _elementos(n, vector<TIPO>(m, 0.0))
{}

template <class TIPO>
matriz<TIPO>::matriz(unsigned int n, unsigned int m, TIPO valorInicial) : _filas(n), _columnas(m), _elementos(n, vector<TIPO>(m, valorInicial))
{}

template <class TIPO>
matriz<TIPO>& matriz<TIPO>::trasponer(matriz<TIPO>& m ) {
	matriz<TIPO> res(m.cant_columnas(), m.cant_filas() );	
	for(unsigned int i=0; i<m.cant_filas();i++){
		for(unsigned int j=0;j< m.cant_columnas();j++){
			res[i][j] = m[j][i];
			
		}
	}
		return res;
}
	

template <class TIPO>
unsigned int matriz<TIPO>::cant_filas()
{
    return _filas;
}

template <class TIPO>
unsigned int matriz<TIPO>::cant_columnas()
{
    return _columnas;
}

template <class TIPO>
void matriz<TIPO>::push_fila(vector<TIPO>& fila)
{
    vector<TIPO> nuevaFila(fila);

    if(nuevaFila.size() < _columnas)
        nuevaFila.resize(_columnas);

    else
    {
		if(nuevaFila.size() > _columnas)
			for(unsigned int i = 0; i < _filas; i++)
				_elementos[i].resize(_columnas);
            
        _columnas = nuevaFila.size();
    }
    
    _elementos.push_back(nuevaFila);
    _filas++;
}

template <class TIPO>
vector<TIPO>& matriz<TIPO>::get_fila(unsigned int fila)
{
	if(fila <= _filas-1) return _elementos[fila];
	else
	{
		cout << "ERROR: Funcion get_fila() llamada con valores fuera de rango!" << endl;
		return NULL;
	}
}

template <class TIPO>
void matriz<TIPO>::set_fila(unsigned int fila, vector<TIPO>& vec)
{
	if(fila <= _filas-1) _elementos[fila] = vec;
}

template <class TIPO>
TIPO matriz<TIPO>::get_elem(unsigned int fila, unsigned int columna)
{
	if(fila > _filas-1 || columna > _columnas-1) cout << "ERROR: Funcion get_elem() llamada con valores fuera de rango!" << endl;
	return _elementos[fila][columna];
}

template <class TIPO>
void matriz<TIPO>::set_elem(unsigned int fila, unsigned int columna, TIPO elem)
{
	if(fila <= _filas-1 && columna <= _columnas-1) _elementos[fila][columna] = elem;
}

template <class TIPO>
vector<TIPO>& matriz<TIPO>::operator[](unsigned int fila)
{
	if(fila >_filas-1) cout << "ERROR: Operador [] llamado con valores fuera de rango! intentaste acceder a  "<< fila << endl;
	return _elementos[fila];
}

template <class TIPO>
void matriz<TIPO>::mostrar(std::ostream& out) const
{
    for(unsigned int i=0; i<_filas; i++)
    {
        out << "| ";
        for(unsigned int j=0; j<_columnas; j++)
        {
            out << _elementos[i][j];
            if (j<_columnas-1) out << "  ";
        }
        out << " |";
        if (i <_filas-1) out << endl;
    }
}

template <class TIPO>
ostream& operator<<(ostream& out, const matriz<TIPO>& m) {
    m.mostrar(out);
	return out;
}

#endif
